package com.lagou.edu.factory;

import com.lagou.edu.anno.Autowire;
import com.lagou.edu.anno.Service;
import com.lagou.edu.anno.Transactional;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.File;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLDecoder;
import java.util.*;

/**
 * @author 应癫
 *
 * 工厂类，生产对象（使用反射技术）
 */
public class BeanFactory {

    /**
     * 任务一：读取解析xml，通过反射技术实例化对象并且存储待用（map集合）
     * 任务二：对外提供获取实例对象的接口（根据id获取）
     */

    private static Map<String,Object> map = new HashMap<>();  // 存储对象
    private static List<String> classList = new ArrayList<>();  //存储包下的全类名


    static {
        // 任务一：读取解析xml，通过反射技术实例化对象并且存储待用（map集合）
        // 加载xml
        InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
        // 解析xml
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read(resourceAsStream);
            Element rootElement = document.getRootElement();
            List<Element> beanList = rootElement.selectNodes("//bean");
            for (int i = 0; i < beanList.size(); i++) {
                Element element =  beanList.get(i);
                // 处理每个bean元素，获取到该元素的id 和 class 属性
                String id = element.attributeValue("id");        // accountDao
                String clazz = element.attributeValue("class");  // com.lagou.edu.dao.impl.JdbcAccountDaoImpl
                // 通过反射技术实例化对象
                Class<?> aClass = Class.forName(clazz);
                Object o = aClass.newInstance();  // 实例化之后的对象

                // 存储到map中待用
                map.put(id,o);

            }

            // 实例化完成之后维护对象的依赖关系，检查哪些对象需要传值进入，根据它的配置，我们传入相应的值
            // 有property子元素的bean就有传值需求
            List<Element> propertyList = rootElement.selectNodes("//property");
            // 解析property，获取父元素
            for (int i = 0; i < propertyList.size(); i++) {
                Element element =  propertyList.get(i);   //<property name="AccountDao" ref="accountDao"></property>
                String name = element.attributeValue("name");
                String ref = element.attributeValue("ref");

                // 找到当前需要被处理依赖关系的bean
                Element parent = element.getParent();

                // 调用父元素对象的反射功能
                String parentId = parent.attributeValue("id");
                Object parentObject = map.get(parentId);
                // 遍历父对象中的所有方法，找到"set" + name
                Method[] methods = parentObject.getClass().getMethods();
                for (int j = 0; j < methods.length; j++) {
                    Method method = methods[j];
                    if(method.getName().equalsIgnoreCase("set" + name)) {  // 该方法就是 setAccountDao(AccountDao accountDao)
                        method.invoke(parentObject,map.get(ref));
                    }
                }

                // 把处理之后的parentObject重新放到map中
                map.put(parentId,parentObject);

            }
            //注解解析，由于简单实现仅解析@Service,@Autowire,@Transactional,其余bean仍然使用以上的xml解析方式处理
            //处理xml中自定义的包扫描标签
            List<Element> packageList = rootElement.selectNodes("//packageScan");
            for (Element element : packageList) {
                URL resource = BeanFactory.class.getClassLoader().getResource(element.getTextTrim().replaceAll("\\.","/"));
                //使用URLDecoder.decode方法解决文件夹路径存在中文显示异常问题
                //note:假如路径中存在空格，此处仍然可能存在问题，因为简单测试，不再做处理
                String path = resource.getPath();
                String decodepath = URLDecoder.decode(path,"UTF-8");
                File file = new File(decodepath);
                //扫描该package下全部class,获得全路径名放入List集合
                scanClassList(file);
            }

            //解析Service注解
            for (String classfullpath : classList) {
                Class<?> aClass = Class.forName(classfullpath);
                //获取class中的全部注解
                Annotation[] annotations = aClass.getAnnotations();
                for (Annotation annotation : annotations) {
                    //解析@Service注解
                    if (annotation.annotationType().equals(Service.class)){
                        Annotation declaredAnnotation = aClass.getDeclaredAnnotation(annotation.annotationType());
                        Method declaredMethod = declaredAnnotation.getClass().getDeclaredMethod("value");
                        //Service value()返回String
                        String beanId = (String)declaredMethod.invoke(annotation);
                        //此处判断@Service注解有无value值
                        if(beanId.equals("") || beanId==null){
                            String className = aClass.getName();
                            //无注解值获取类名(第一位小写)
                            beanId = lowerFirstChar(className);
                        }
                        map.put(beanId,aClass.newInstance());

                    }
                }

            }

            //解析@Autowire
            //遍历已放入工厂管理的bean中存在的@Autowire注解，并处理注入
            Set<Map.Entry<String, Object>> entries = map.entrySet();
            for (Map.Entry<String, Object> entry : entries) {
                Object value = entry.getValue();
                //获取bean中的所有属性
                Field[] declaredFields = value.getClass().getDeclaredFields();
                for (Field declaredField : declaredFields) {
                    if (declaredField.isAnnotationPresent(Autowire.class)){
                        declaredField.setAccessible(true);
                        declaredField.set(value,map.get(declaredField.getName()));
                    }
                }
            }

            //解析@Transactional
            for (Map.Entry<String, Object> entry : entries) {
                Object value = entry.getValue();
                boolean hasTransaction = false;
                //判断该类或者该类的方法，是否拥有@Transactional标签
                if (value.getClass().isAnnotationPresent(Transactional.class)){
                    hasTransaction = true;
                }else{
                    Method[] methods = value.getClass().getMethods();
                    for (Method method : methods) {
                        if (method.isAnnotationPresent(Transactional.class)){
                            hasTransaction = true;
                            break;
                        }
                    }
                }
                //对于@Transactional,直接获取其代理对象，并放入IoC中，以供后续使用
                if(hasTransaction){
                    ProxyFactory proxyFactory =(ProxyFactory) getBean("proxyFactory");
                    //此处判断对象是否实现了接口，实现接口则使用jdk代理，未实现接口则使用cglib代理
                    if (value.getClass().getInterfaces().length>0){
                        Object jdkProxy = proxyFactory.getJdkProxy(value);
                        map.put(entry.getKey(),jdkProxy);
                    }else{
                        Object cglibProxy = proxyFactory.getCglibProxy(value);
                        map.put(entry.getKey(),cglibProxy);
                    }

                }


            }


        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

    }


    // 任务二：对外提供获取实例对象的接口（根据id获取）
    public static  Object getBean(String id) {
        return map.get(id);
    }

    //扫描获取路径下所有class
    private static void scanClassList(File file){
        if (file.isFile()){
            String path = file.getPath();
            String suffix = path.substring(path.lastIndexOf(".") + 1, path.length());
            if (suffix.equals("class")){
                int startNum = file.getPath().indexOf("com");
                //替换符号为类的全路径名
                classList.add(file.getPath().substring(startNum, file.getPath().length() - 6).replaceAll("/", "."));
            }
        } else if (file.isDirectory()){
            File[] files = file.listFiles();
            for (File file1 : files) {
                scanClassList(file1);
            }
        }
    }

    //当@Service注解无value值，使用该方法将类名首字母小写返回作为beanId
    private static String lowerFirstChar(String s){
        //从全路径名获取类名

        if(Character.isLowerCase(s.charAt(0))) {
            return s;
        } else {
            return (new StringBuilder()).append(Character.toLowerCase(s.charAt(0))).append(s.substring(1)).toString();
        }
    }

}
